/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/db/notebook_table.h>
#include <journal/models/db/common_table.h>

using namespace std;
using namespace journal::model::db;

const b::uint32 notebook_table::singletonId = 0;

notebook_table::notebook_table()
{
    _blobCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
}

std::shared_ptr<journal::model::db::inotebook_table> notebook_table::create(std::shared_ptr<s::iengine> dbEngine)
{
    std::shared_ptr<journal::model::db::notebook_table> returnValue = make_shared<notebook_table>();
    returnValue->_commonTableClient = std::dynamic_pointer_cast<icommon_table_client>(returnValue);
    returnValue->_commonTable = createCommonTable(returnValue->_commonTableClient, dbEngine, L"Notebook");
    return returnValue;
}

std::shared_ptr<icommon_table> notebook_table::common()
{
    return _commonTable;
}

void notebook_table::defineColumns(std::shared_ptr<s::itable> table)
{
    BAJA_LOCK(_mutex);

    _notebookIdColumn = s::createColumn<b::uint32>(L"ID", s::column_type::type_int32);
    table->addColumn(_notebookIdColumn);
    
    _notebookBlobSizeColumn = s::createColumn<b::uint32>(L"ThumbnailSize", s::column_type::type_int32);
    _notebookBlobSizeColumn->setIsBlobSize(true);
    table->addColumn(_notebookBlobSizeColumn);

    // _notebookBlobColumn = s::createColumn<s::binary_blob>(L"Thumbnail", s::column_type::type_binaryBlob);
    _notebookBlobColumn = s::createBlobColumn(L"Thumbnail", s::column_type::type_binaryBlob);
    _notebookBlobColumn->setFixed(false);
    table->addColumn(_notebookBlobColumn);
}

void notebook_table::defineIndexColumns(std::shared_ptr<s::iindex> primaryKeyIndex)
{
    BAJA_LOCK(_mutex);

    _notebookIdIndexColumn = s::createIndexColumnDefinition(_notebookIdColumn, true);
    primaryKeyIndex->addIndexColumn(_notebookIdIndexColumn);
}

void notebook_table::defineRowCells(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> row)
{
    BAJA_LOCK(_mutex);

    // Bug 322: TRACK: can we get rid of this 'cell' concept?
    _notebookIdCell = _notebookIdColumn->createCell(0);
    _notebookBlobSizeCell = _notebookBlobSizeColumn->createCell(0);
    _notebookBlobCell = _notebookBlobColumn->createCellByMoving(s::binary_blob());

    row->push_back(_notebookIdCell);
    row->push_back(_notebookBlobSizeCell);
    row->push_back(_notebookBlobCell);

    // do not add the thumbnail bits to the default row because they are optional and are not
    // typically set along the other bits.

    _blobCells->push_back(_notebookIdCell);
    _blobCells->push_back(_notebookBlobSizeCell);
    _blobCells->push_back(_notebookBlobCell);
}

std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> notebook_table::findRow(const b::uint32& id)
{
    BAJA_LOCK(_mutex);

    // select columns...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_notebookIdColumn);
    result->addColumn(_notebookBlobSizeColumn);
    result->addColumn(_notebookBlobColumn);

    // where id = id
    _notebookIdCell->setValue(id);
    _commonTable->read(_commonTable->stagingRow(),
                        static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()),
                       result);

    // iterate through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    BAJA_ASSERT((rows->size() == 1 || rows->size() == 0), L"only 0 or 1 row is expected!");

    return rows->size() ? rows : nullptr;
}

void notebook_table::write(
    const b::uint32& id,
    s::binary_blob& inBlob)
{
    BAJA_LOCK(_mutex);

    _notebookIdCell->setValue(id);
    _notebookBlobSizeCell->setValue(inBlob._size);
    _notebookBlobCell->setAppendOnUpdate(false);
    _notebookBlobCell->setValueByMoving(inBlob);

    if ( nullptr == findRow(id) )
    {
        _commonTable->insert(_commonTable->stagingRow());
    }
    else
    {
        _commonTable->update(_commonTable->stagingRow(),
            static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()));
    }
}

void notebook_table::write(
    const std::vector<size_t>& v)
{
    BAJA_LOCK(_mutex);

    if (v.size() == 0)
    {
        s::binary_blob blob;
        write(singletonId, blob);
    }
    else
    {
        s::binary_blob blob(
            static_cast<b::uint32>(v.size() * sizeof(v[0])),
            &v[0]);

#ifdef BAJA_DEBUG
        // We have been getting assertions in notebook_model::getPageNumberFromPageId
        // in seemingly random cases. This could be due to writing bad data into the
        // database. Here we're doing some simple validation, checking that all
        // values are unique. 
        std::vector<size_t> c = v;
        std::sort(c.begin(), c.end());
        for (size_t i = 1, imax=c.size(); i < imax; i++)
        {
            BAJA_VERIFY(c[i] != c[i-1]);
        }
#endif

        write(singletonId, blob);
    }
}

void notebook_table::read(
    const b::uint32& id,
    s::binary_blob& outBlob)
{
    BAJA_LOCK(_mutex);

    // select columns...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_notebookIdColumn);
    result->addColumn(_notebookBlobSizeColumn);
    result->addColumn(_notebookBlobColumn);

    // where table.id = id
    _notebookIdCell->setValue(id);

    _commonTable->read(
        _blobCells,
        1 /* notebookId only */,
        result);

    // iterate through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    BAJA_ASSERT(rows->size() <= 1, L"One (and only one) records is expected.");

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        // Bug 323 : TRACK: Gmileka: It'd be nice to have an easier way of getting directly to the cell we need...
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if (cell->getColumnId() == _notebookIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                if (id == typedCell->getValue())
                {
                    continue;
                }
                else
                {
                    BAJA_ASSERT(false, L"The query must return the requested row only!");
                }
            }
            if (cell->getColumnId() == _notebookBlobColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<s::binary_blob>> typedCell = std::dynamic_pointer_cast<s::icell_typed<s::binary_blob>>(cell);
                typedCell->getValueByMoving(outBlob);
            }
        }
    }
}

void notebook_table::read(
    std::vector<size_t>& v)
{
    BAJA_LOCK(_mutex);

    s::binary_blob blob;
    read(singletonId, blob);
    if (blob._size != 0)
    {
        v.resize(blob._size / sizeof(v[0]));
        memcpy(&v[0], blob._buffer, blob._size);
    }

#ifdef BAJA_DEBUG
    // We have been getting assertions in notebook_model::getPageNumberFromPageId
    // in seemingly random cases. This could be due to reading bad data into the
    // database. Here we're doing some simple validation, checking that all
    // values are unique. 
    std::vector<size_t> c = v;
    std::sort(c.begin(), c.end());
    for (size_t i = 1, imax = c.size(); i < imax; i++)
    {
        BAJA_VERIFY(c[i] != c[i-1]);
    }
#endif
}

std::shared_ptr<inotebook_table> journal::model::db::createNotebookTable(std::shared_ptr<s::iengine> dbEngine)
{
    return notebook_table::create(dbEngine);
}